﻿using System;
using System.IO;
using System.Net;
using System.Text;

namespace WeiboSharp
{
    public partial class Http
    {
        public void DeleteAsync(Action<WeiboResponse> action)
        {
            GetStyleMethodInternalAsync("DELETE", action);
        }

        public void GetAsync(Action<WeiboResponse> action)
        {
            GetStyleMethodInternalAsync("GET", action);
        }

        public void HeadAsync(Action<WeiboResponse> action)
        {
            GetStyleMethodInternalAsync("HEAD", action);
        }

        public void OptionsAsync(Action<WeiboResponse> action)
        {
            GetStyleMethodInternalAsync("OPTIONS", action);
        }

        public void PostAsync(Action<WeiboResponse> action)
        {
            PutPostInternalAsync("POST", action);
        }

        public void PutAsync(Action<WeiboResponse> action)
        {
            PutPostInternalAsync("PUT", action);
        }

        private void GetStyleMethodInternalAsync(string method, Action<WeiboResponse> callback)
        {
            try
            {
                var url = Url;
                var webRequest = ConfigureAsyncWebRequest(method, url);
                webRequest.BeginGetResponse(result => ResponseCallback(result, callback), webRequest);
            }
            catch (Exception ex)
            {
                var response = new WeiboResponse();
                response.ErrorMessage = ex.Message;
                response.ErrorException = ex;
                response.StatusCode = HttpStatusCode.NotImplemented;
                ExecuteCallback(response, callback);
            }
        }

        private void PutPostInternalAsync(string method, Action<WeiboResponse> callback)
        {
            try
            {
                var webRequest = ConfigureAsyncWebRequest(method, Url);
                webRequest = PreparePostBody(webRequest);
                WriteRequestBodyAsync(webRequest, callback);
            }
            catch (Exception ex)
            {
                var response = new WeiboResponse();
                response.ErrorMessage = ex.Message;
                response.ErrorException = ex;
                response.StatusCode = HttpStatusCode.NotImplemented;
                ExecuteCallback(response, callback);
            }
        }

        private void WriteRequestBodyAsync(HttpWebRequest webRequest, Action<WeiboResponse> callback)
        {
            if (HasBody || HasFiles)
            {
                webRequest.ContentLength = CalculateContentLength();

                webRequest.BeginGetRequestStream(result => RequestStreamCallback(result, callback), webRequest);
            }
            else
            {
                webRequest.BeginGetResponse(r => ResponseCallback(r, callback), webRequest);
            }
        }

        private long CalculateContentLength()
        {
            if (!HasFiles)
            {
                return RequestBody.Length;
            }

            // calculate length for multipart form
            long length = 0;
            foreach (var file in Files)
            {
                length += GetMultipartFileHeader(file).Length;
                length += file.ContentLength;
                length += Environment.NewLine.Length;
            }

            foreach (var param in Parameters)
            {
                length += GetMultipartFormData(param).Length;
            }

            length += GetMultipartFooter().Length;
            return length;
        }

        private void WriteMultipartFormDataAsync(Stream requestStream)
        {
            var encoding = Encoding.UTF8;
            foreach (var file in Files)
            {
                // Add just the first part of this param, since we will write the file data directly to the Stream
                var header = GetMultipartFileHeader(file);
                requestStream.Write(encoding.GetBytes(header), 0, header.Length);

                // Write the file data directly to the Stream, rather than serializing it to a string.
                file.Writer(requestStream);
                var lineEnding = Environment.NewLine;
                requestStream.Write(encoding.GetBytes(lineEnding), 0, lineEnding.Length);
            }

            foreach (var param in Parameters)
            {
                var postData = GetMultipartFormData(param);
                requestStream.Write(encoding.GetBytes(postData), 0, postData.Length);
            }

            var footer = GetMultipartFooter();
            requestStream.Write(encoding.GetBytes(footer), 0, footer.Length);
        }

        private void RequestStreamCallback(IAsyncResult result, Action<WeiboResponse> callback)
        {
            var webRequest = (HttpWebRequest)result.AsyncState;

            // write body to request stream
            using (var requestStream = webRequest.EndGetRequestStream(result))
            {
                if (HasFiles)
                {
                    WriteMultipartFormDataAsync(requestStream);
                }
                else
                {
                    var encoding = Encoding.UTF8;
                    requestStream.Write(encoding.GetBytes(RequestBody), 0, RequestBody.Length);
                }
            }

            webRequest.BeginGetResponse(r => ResponseCallback(r, callback), webRequest);
        }

        private void GetRawResponseAsync(IAsyncResult result, Action<HttpWebResponse> callback)
        {
            var response = new WeiboResponse();
            response.StatusCode = HttpStatusCode.NotImplemented;

            HttpWebResponse raw = null;

            try
            {
                var webRequest = (HttpWebRequest)result.AsyncState;
                raw = webRequest.EndGetResponse(result) as HttpWebResponse;
            }
            catch (WebException ex)
            {
                if (ex.Response is HttpWebResponse)
                {
                    raw = ex.Response as HttpWebResponse;
                }
            }

            callback(raw);
        }

        private void ResponseCallback(IAsyncResult result, Action<WeiboResponse> callback)
        {
            var response = new WeiboResponse();
            response.StatusCode = HttpStatusCode.NotImplemented;

            try
            {
                GetRawResponseAsync(result, webResponse =>
                {
                    response = ExtractResponseData(webResponse);
                    ExecuteCallback(response, callback);
                });
            }
            catch (Exception ex)
            {
                response.ErrorMessage = ex.Message;
                response.ErrorException = ex;
                response.StatusCode = HttpStatusCode.NotImplemented;
                ExecuteCallback(response, callback);
            }
        }

        private void ExecuteCallback(WeiboResponse response, Action<WeiboResponse> callback)
        {
            callback(response);
        }

        private HttpWebRequest ConfigureAsyncWebRequest(string method, Uri url)
        {

            var webRequest = (HttpWebRequest)WebRequest.Create(url);
            webRequest.UseDefaultCredentials = false;

            webRequest = AppendHeaders(webRequest);

            webRequest.Method = method;

            if (!HasFiles)
            {
                webRequest.ContentLength = 0;
            }

            if (Credentials != null)
            {
                webRequest.Credentials = Credentials;
            }

            if (!string.IsNullOrWhiteSpace(UserAgent))
            {
                webRequest.UserAgent = UserAgent;
            }

            ServicePointManager.Expect100Continue = false;

            if (Timeout != 0)
            {
                webRequest.Timeout = Timeout;
            }

            if (Proxy != null)
            {
                webRequest.Proxy = Proxy;
            }

            if (FollowRedirects && MaxRedirects.HasValue)
            {
                webRequest.MaximumAutomaticRedirections = MaxRedirects.Value;
            }

            webRequest.AllowAutoRedirect = FollowRedirects;

            return webRequest;
        }
    }
}
